/*
 * XML.java
 *
 * Created on June 3, 2005, 1:27 PM
 *
* (C) R. Alexander Milowski alex@milowski.com
 */

package com.milowski.monos;

import java.util.*;
import java.util.regex.*;
import java.net.*;

import com.milowski.monos.monomial.*;
import com.milowski.monos.binomial.*;
import com.milowski.monos.tools.Shell;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;

/**
 *
 * @author R. Alexander Milowski
 */
public class XML {

   public static final URI STEPS_NAMESPACE = InfosetFactory.createNamespace("http://www.milowski.com/Vocabulary/Math/Monos/Steps/2005/1/0");
   public static final URI VOCABULARY = InfosetFactory.createNamespace("http://www.milowski.com/Vocabulary/Math/Monos/2005/1/0");
   
   // Monos Vocabulary
   public static final Name vectorName = InfosetFactory.createName(VOCABULARY,"vector");
   public static final Name expressionName = InfosetFactory.createName(VOCABULARY,"expression");
   public static final Name errorName = InfosetFactory.createName(VOCABULARY,"error");
   public static final Name computeBasisName = InfosetFactory.createName(VOCABULARY,"compute-basis");
   public static final Name computeToricIdealName = InfosetFactory.createName(VOCABULARY,"compute-toric-ideal");
   public static final Name matrixName = InfosetFactory.createName(VOCABULARY,"matrix");
   public static final Name ilistName = InfosetFactory.createName(VOCABULARY,"ilist");
   public static final Name lexedListName = InfosetFactory.createName(VOCABULARY,"lexed-list");
   public static final Name lexOrderName = InfosetFactory.createName(VOCABULARY,"lex-order");
   public static final Name decomposeName = InfosetFactory.createName(VOCABULARY,"decompose");
   public static final Name intersectName = InfosetFactory.createName(VOCABULARY,"intersect");
   public static final Name monomialIdealName = InfosetFactory.createName(VOCABULARY,"monomial-ideal");
   public static final Name monomialName = InfosetFactory.createName(VOCABULARY,"monomial");
   public static final Name binomialIdealName = InfosetFactory.createName(VOCABULARY,"binomial-ideal");
   public static final Name binomialName = InfosetFactory.createName(VOCABULARY,"binomial");
   public static final Name cbinomialName = InfosetFactory.createName(VOCABULARY,"cbinomial");
   public static final Name weightName = InfosetFactory.createName(VOCABULARY,"weight");
   public static final Name costName = InfosetFactory.createName(VOCABULARY,"cost");
   public static final Name linearProgramName = InfosetFactory.createName(VOCABULARY,"linear-program");
   public static final Name solutionName = InfosetFactory.createName(VOCABULARY,"solution");
   public static final Name pointName = InfosetFactory.createName(VOCABULARY,"point");
   public static final Name formulateGapName = InfosetFactory.createName(VOCABULARY,"formulate-gap");
   public static final Name componentLPName = InfosetFactory.createName(VOCABULARY,"component-lp");
   public static final Name componentLPListName = InfosetFactory.createName(VOCABULARY,"component-lp-list");
   public static final Name cName = InfosetFactory.createName(VOCABULARY,"c");
   public static final Name bName = InfosetFactory.createName(VOCABULARY,"b");
   public static final Name uName = InfosetFactory.createName(VOCABULARY,"u");
   public static final Name rowSpaceVectorName = InfosetFactory.createName(VOCABULARY,"row-space-vector");
   public static final Name reorderName = InfosetFactory.createName(VOCABULARY,"reorder");

   private static Map prefixes = new HashMap();
   private static Map topLevel = new HashMap();
   private static MonomialIdealUnmarshaller monomialIdealUnmarshaller = new MonomialIdealUnmarshaller();
   private static LexedListUnmarshaller lexedListUnmarshaller = new LexedListUnmarshaller();
   private static LexOrderUnmarshaller lexOrderUnmarshaller = new LexOrderUnmarshaller();
   private static MonomialUnmarshaller monomialUnmarshaller = new MonomialUnmarshaller();
   private static BinomialUnmarshaller binomialUnmarshaller = new BinomialUnmarshaller();
   private static CompactBinomialUnmarshaller compactBinomialUnmarshaller = new CompactBinomialUnmarshaller();
   private static BinomialIdealUnmarshaller binomialIdealUnmarshaller = new BinomialIdealUnmarshaller();

   static {
      prefixes.put("m",VOCABULARY);
      prefixes.put("mml",MathML.MATHML);
      try {
         com.milowski.monos.binomial.MatrixBasis.register();
         registerUnmarshaller(lexOrderName,lexOrderUnmarshaller);
         registerUnmarshaller(monomialName,monomialUnmarshaller);
         registerUnmarshaller(monomialIdealName,monomialIdealUnmarshaller);
         registerUnmarshaller(lexedListName,lexedListUnmarshaller);
         registerUnmarshaller(weightName,monomialUnmarshaller);
         registerUnmarshaller(costName,monomialUnmarshaller);
         registerUnmarshaller(binomialName,binomialUnmarshaller);
         registerUnmarshaller(cbinomialName,compactBinomialUnmarshaller);
         registerUnmarshaller(binomialIdealName,binomialIdealUnmarshaller);
      } catch (XMLException ex) {
         throw new RuntimeException("Cannot register unmarshaller for types due to exception.",ex);
      }
   }
   
   static class MonomialIdealUnmarshaller implements XMLUnmarshaller {
      public Object fromXML(Element representation) throws XMLException {
         if (!representation.getName().equals(monomialIdealName)) {
            throw new XMLException("Cannot unmarshall monomial ideal from "+representation.getName());
         }
         Iterator<Element> children = representation.getElementChildren();
         if (!children.hasNext()) {
            throw new XMLException("Missing "+lexOrderName+" for "+monomialIdealName);
         }
         Element lexOrderRep = children.next();
         if (lexOrderRep.getName().equals(expressionName)) {
            if (children.hasNext()) {
               throw new XMLException(((Element)children.next()).getName()+" unexpected in "+monomialIdealName);
            }
            try {
               Shell.init();
               MonomialIdeal ideal = (MonomialIdeal)Shell.eval(lexOrderRep.getText());
               return ideal;
            } catch (Throwable ex) {
               throw new XMLException("Cannot evaluate monomial ideal expression.",ex);
            }
         }
         if (lexOrderRep.getName().equals(lexOrderName)) {
            LexOrder lexOrder = (LexOrder)lexOrderUnmarshaller.fromXML(lexOrderRep);
            MonomialIdeal ideal = new MonomialIdeal(lexOrder);
            while (children.hasNext()) {
               ideal.add(monomialUnmarshaller.fromXML((Element)children.next()));
            }
            return ideal;
         } else if (lexOrderRep.getName().equals(matrixName)) {
            LexOrder lexOrder = new LexOrder();
            if (children.hasNext()) {
               throw new XMLException("Unexpected element "+((Element)children.next()).getName());
            }
            Iterator<Element> lists = lexOrderRep.getElementChildren();
            if (!lists.hasNext()) {
               throw new XMLException("The matrix contains no list element.");
            }
            Monomial m = (Monomial)monomialUnmarshaller.fromXML(lists.next());
            int len = m.getValues().length;
            for (int i=1; i<=len; i++) {
               lexOrder.add("x"+i);
            }
            MonomialIdeal ideal = new MonomialIdeal(lexOrder);
            ideal.add(m);
            while (lists.hasNext()) {
               ideal.add(monomialUnmarshaller.fromXML(lists.next()));
            }
            return ideal;
         } else if (!lexOrderRep.getName().equals(monomialName)) {
            throw new XMLException("Expecting "+monomialName+" but found "+lexOrderRep.getName());
         } else {
            LexOrder lexOrder = new LexOrder();
            Monomial m = (Monomial)monomialUnmarshaller.fromXML(lexOrderRep);
            int len = m.getValues().length;
            for (int i=1; i<=len; i++) {
               lexOrder.add("x"+i);
            }
            MonomialIdeal ideal = new MonomialIdeal(lexOrder);
            ideal.add(m);
            while (children.hasNext()) {
               ideal.add(monomialUnmarshaller.fromXML(children.next()));
            }
            return ideal;
         }
         
      }
   }

   static class LexedListUnmarshaller implements XMLUnmarshaller {
      public Object fromXML(Element representation) throws XMLException {
         if (!representation.getName().equals(lexedListName)) {
            throw new XMLException("Cannot unmarshall lexed list ideal from "+representation.getName());
         }
         Iterator<Element> children = representation.getElementChildren();
         if (!children.hasNext()) {
            throw new XMLException("Missing "+lexOrderName+" for "+monomialIdealName);
         }
         Element lexOrderRep = children.next();
         if (lexOrderRep.getName().equals(lexOrderName)) {
            LexOrder lexOrder = (LexOrder)lexOrderUnmarshaller.fromXML(lexOrderRep);
            LexOrderedList list = new LexOrderedList(lexOrder);
            while (children.hasNext()) {
               list.add(XML.unmarshall(children.next()));
            }
            return list;
         } else {
            throw new XMLException("Expecting "+lexOrderName+" but found "+lexOrderRep.getName());
         }
         
      }
   }

   static class LexOrderUnmarshaller implements XMLUnmarshaller {
      static Pattern pattern = Pattern.compile("\\s+");
      public Object fromXML(Element representation) throws XMLException {
         if (!representation.getName().equals(lexOrderName)) {
            throw new XMLException("Cannot unmarshall lex order from "+representation.getName());
         }
         LexOrder order = new LexOrder();
         String [] vars = pattern.split(representation.getText().trim());
         for (int i=0; i<vars.length; i++) {
            order.add(vars[i]);
         }
         return order;
      }
   }
   
   static class MonomialUnmarshaller implements XMLUnmarshaller {
      // TODO: use Monomial.fromXML
      static Pattern pattern = Pattern.compile("\\s+");
      public Object fromXML(Element representation) throws XMLException {
         String [] svalues = pattern.split(representation.getText().trim());
         Monomial m = new Monomial(svalues.length);
         short [] values = m.getValues();
         for (int i=0; i<values.length; i++) {
            values[i] = Short.parseShort(svalues[i]);
         }
         return m;
      }
   }

   static class BinomialUnmarshaller implements XMLUnmarshaller {
      static Pattern pattern = Pattern.compile("\\s+");
      public Object fromXML(Element representation) throws XMLException {
         Iterator<Element> children = representation.getElementChildren();
         if (!children.hasNext()) {
            throw new XMLException("Expecting "+monomialName+" but no element children were present.");
         }
         Element leadingRep = children.next();
         if (!leadingRep.getName().equals(monomialName)) {
            throw new XMLException("Expecting "+monomialName+" but found "+leadingRep.getName());
         }
         if (!children.hasNext()) {
            throw new XMLException("Expecting "+monomialName+" but only one child was present.");
         }
         Element trailingRep = children.next();
         if (!trailingRep.getName().equals(monomialName)) {
            throw new XMLException("Expecting "+monomialName+" but found "+trailingRep.getName());
         }
         short [] leading = process(leadingRep);
         short [] trailing = process(trailingRep);
         if (leading.length!=trailing.length) {
            throw new XMLException("Monomial are not of the same length.");
         }
         return new FullBinomial(leading,trailing,false);
      }
      
      short [] process(Element representation) {
         String [] svalues = pattern.split(representation.getText().trim());
         short [] values = new short[svalues.length];
         for (int i=0; i<values.length; i++) {
            values[i] = Short.parseShort(svalues[i]);
         }
         return values;
      }
   }

   static class CompactBinomialUnmarshaller implements XMLUnmarshaller {
      static Pattern pattern = Pattern.compile("\\s+");
      public Object fromXML(Element representation) throws XMLException {
         String [] svalues = pattern.split(representation.getText().trim());
         CompactBinomial m = new CompactBinomial(svalues.length);
         short [] values = m.getValues();
         for (int i=0; i<values.length; i++) {
            values[i] = Short.parseShort(svalues[i]);
         }
         return m;
      }
   }

   static class BinomialIdealUnmarshaller implements XMLUnmarshaller {
      public Object fromXML(Element representation) throws XMLException {
         return BinomialIdeal.fromXML(representation);
      }
   }

   /** Creates a new instance of XML */
   private XML() {
   }

   public static void error(ItemDestination dest,String message) 
      throws XMLException
   {
      error(InfosetFactory.getDefaultInfoset().createItemConstructor(),dest,message);
   }

   public static void error(ItemConstructor constructor,ItemDestination dest,String message) 
      throws XMLException
   {
      dest.send(constructor.createElement(XML.errorName));
      dest.send(constructor.createCharacters(message));
      dest.send(constructor.createElementEnd(errorName));
   }

   public static void registerUnmarshaller(Name element,XMLUnmarshaller unmarshaller)
      throws XMLException
   {
      Object other;
      if ((other=topLevel.get(element))!=null) {
         throw new XMLException("Top-level unmarshaller "+other.getClass().getName()+" already registered for "+element);
      }
      topLevel.put(element,unmarshaller);
   }

/*
   public static void registerUnmarshaller(Name parent,Name element,XMLUnmarshaller unmarshaller)
      throws XMLException
   {
   }*/

   public static Object unmarshall(Element representation) 
      throws XMLException
   {
      XMLUnmarshaller unmarshaller = (XMLUnmarshaller)topLevel.get(representation.getName());
      if (unmarshaller==null) {
         throw new XMLException("Unknown element "+representation.getName());
      }
      return unmarshaller.fromXML(representation);
   }

   public static Object unmarshall(Document doc) 
      throws XMLException
   {
      return unmarshall(doc.getDocumentElement());
   }
   
}
